/**
 * This tests that all the different commands for role manipulation all properly handle invalid
 * and atypical inputs.
 */

function runTest(conn) {
    var db = conn.getDB('test');
    var admin = conn.getDB('admin');
    admin.createUser({user: 'userAdmin', pwd: 'pwd', roles: ['userAdminAnyDatabase']});
    admin.auth('userAdmin', 'pwd');

    (function testCreateRole() {
         jsTestLog("Testing createRole");

         // Role with no privs
         db.createRole({role: "role1", roles: [], privileges: []});

         // Role with duplicate other roles
         db.createRole({role: "role2",
                        roles: ['read', 'read', 'role1', 'role1'],
                        privileges: []});
         assert.eq(2, db.getRole("role2").roles.length);

         // Role with duplicate privileges
         db.createRole({role: "role3",
                        roles: ['role2'],
                        privileges: [{resource: {db: 'test', collection: ''},
                                      actions:['find']},
                                     {resource: {db: 'test', collection: ''},
                                      actions:['find']}]});
         assert.eq(1, db.getRole("role3", {showPrivileges: true}).privileges.length);

         // Try to create role that already exists.
         assert.throws(function() {
                           db.createRole({role: 'role2', roles:[], privileges:[]});
                       });


         // Try to create role with no name
         assert.throws(function() {
                           db.createRole({role: '', roles:[], privileges:[]});
                       });

         // Try to create a role the wrong types for the arguments
         assert.throws(function() {
                           db.createRole({role: 1, roles:[], privileges:[]});
                       });
         assert.throws(function() {
                           db.createRole({role: ["role4", "role5"],
                                          roles:[],
                                          privileges:[]});
                       });
         assert.throws(function() {
                           db.createRole({role: 'role6', roles:1, privileges:[]});
                       });
         assert.throws(function() {
                           db.createRole({role: 'role7', roles:[], privileges:1});
                       });

         // Try to create a role with an invalid privilege
         assert.throws(function() {
                           db.createRole({role: 'role8',
                                          roles:[],
                                          privileges:[{resource: {},
                                                       actions:['find']}]});
                       });
         assert.throws(function() {
                           db.createRole({role: 'role9',
                                          roles:[],
                                          privileges:[{resource: {db:"test", collection:"foo"},
                                                       actions:[]}]});
                       });
         assert.throws(function() {
                           db.createRole({role: 'role10',
                                          roles:[],
                                          privileges:[{resource: {db:"test"},
                                                       actions:['find']}]});
                       });
         assert.throws(function() {
                           db.createRole({role: 'role11',
                                          roles:[],
                                          privileges:[{resource: {collection:"foo"},
                                                       actions:['find']}]});
                       });
         assert.throws(function() {
                           db.createRole({role: 'role12',
                                          roles:[],
                                          privileges:[{resource: {anyResource: false},
                                                       actions:['find']}]});
                       });
         assert.throws(function() {
                           db.createRole({role: 'role13',
                                          roles:[],
                                          privileges:[{resource: {db:"test",
                                                                  collection:"foo",
                                                                  cluster:true},
                                                       actions:['find']}]});
                       });
         assert.throws(function() {
                           db.createRole({role: 'role14',
                                          roles:[],
                                          privileges:[{resource: {cluster: false},
                                                       actions:['find']}]});
                       });
         assert.throws(function() {
                           db.createRole({role: 'role15',
                                          roles:[],
                                          privileges:[{resource: {db:"test", collection:"$cmd"},
                                                       actions:['find']}]});
                       });
         assert.throws(function() {
                           db.createRole({role: 'role16',
                                          roles:[],
                                          privileges:[{resource: {db:"test", collection:"foo"},
                                                       actions:['fakeAction']}]});
                       });

         // Try to create role containing itself in its roles array
         assert.throws(function() {
                           db.createRole({role: 'role17',
                                          roles:['role10'],
                                          privileges:[]});
                       });


         assert.eq(3, db.getRoles().length);
     })();

    (function testUpdateRole() {
         jsTestLog("Testing updateRole");

         // Try to update role that doesn't exist
         assert.throws(function() {
                           db.updateRole("fakeRole", {roles: []});
                       });

         // Try to update role to have a role that doesn't exist
         assert.throws(function() {
                           db.updateRole("role1", {roles: ['fakeRole']});
                       });

         // Try to update a built-in role
         assert.throws(function() {
                           db.updateRole("read", {roles: ['readWrite']});
                       });

         // Try to create a cycle in the role graph
         assert.throws(function() {
                           db.updateRole("role1", {roles: ['role1']});
                       });
         assert.eq(0, db.getRole('role1').roles.length);

         assert.throws(function() {
                           db.updateRole("role1", {roles: ['role2']});
                       });
         assert.eq(0, db.getRole('role1').roles.length);

         assert.throws(function() {
                           db.updateRole("role1", {roles: ['role3']});
                       });
         assert.eq(0, db.getRole('role1').roles.length);
     })();

    (function testGrantRolesToRole() {
         jsTestLog("Testing grantRolesToRole");

         // Grant role1 to role2 even though role2 already has role1
         db.grantRolesToRole("role2", ['role1']);
         assert.eq(2, db.getRole('role2').roles.length);

         // Try to grant a role that doesn't exist
         assert.throws(function() {
                           db.grantRolesToRole("role1", ['fakeRole']);
                       });

         // Try to grant *to* a role that doesn't exist
         assert.throws(function() {
                           db.grantRolesToRole("fakeRole", ['role1']);
                       });

         // Must specify at least 1 role
         assert.throws(function() {
                           db.grantRolesToRole("role1", []);
                       });

         // Try to grant to a built-in role
         assert.throws(function() {
                           db.grantRolesToRole("read", ['role1']);
                       });

         // Try to create a cycle in the role graph
         assert.throws(function() {
                           db.grantRolesToRole("role1", ['role1']);
                       });
         assert.eq(0, db.getRole('role1').roles.length);

         assert.throws(function() {
                           db.grantRolesToRole("role1", ['role2']);
                       });
         assert.eq(0, db.getRole('role1').roles.length);

         assert.throws(function() {
                           db.grantRolesToRole("role1", ['role3']);
                       });
         assert.eq(0, db.getRole('role1').roles.length);
     })();

    (function testRevokeRolesFromRole() {
         jsTestLog("Testing revokeRolesFromRole");

         // Try to revoke a role that doesn't exist
         // Should not error but should do nothing.
         assert.doesNotThrow(function() {
                                 db.revokeRolesFromRole("role2", ['fakeRole']);
                       });

         // Try to revoke role3 from role2 even though role2 does not contain role3.
         // Should not error but should do nothing.
         assert.doesNotThrow(function() {
                                 db.revokeRolesFromRole("role2", ['role3']);
                       });
         assert.eq(2, db.getRole("role2").roles.length);

         // Must revoke something
         assert.throws(function() {
                           db.revokeRolesFromRole("role2", []);
                       });

         // Try to remove from built-in role
         assert.throws(function() {
                           db.revokeRolesFromRole("readWrite", ['read']);
                       });

     })();

    (function testGrantPrivilegesToRole() {
         jsTestLog("Testing grantPrivilegesToRole");

         // Must grant something
         assert.throws(function() {
                           db.grantPrivilegesToRole("role1", []);
                       });

         var basicPriv = {resource: {db: 'test', collection:""}, actions: ['find']};

         // Invalid first argument
         assert.throws(function() {
                           db.grantPrivilegesToRole(["role1", "role2"], [basicPriv]);
                       });

         // Try to grant to role that doesn't exist
         assert.throws(function() {
                           db.grantPrivilegesToRole("fakeRole", [basicPriv]);
                       });

         // Test with invalid privileges
         var badPrivs = [];
         badPrivs.push("find");
         badPrivs.push({resource: {db: 'test', collection:""}, actions: ['fakeAction']});
         badPrivs.push({resource: {db: ['test'], collection:""}, actions: ['find']});
         badPrivs.push({resource: {db: 'test', collection:""}});
         badPrivs.push({actions: ['find']});
         badPrivs.push({resource: {db: 'test', collection:""}, actions: []});
         badPrivs.push({resource: {db: 'test'}, actions: ['find']});
         badPrivs.push({resource: {collection: 'test'}, actions: ['find']});
         badPrivs.push({resource: {}, actions: ['find']});
         badPrivs.push({resource: {db: 'test', collection:"", cluster:true }, actions: ['find']});

         for (var i = 0; i < badPrivs.length; i++) {
             assert.throws(function() {
                               db.grantPrivilegesToRole("role1", [badPrivs[i]]);
                           });
         }

         assert.eq(0, db.getRole('role1', {showPrivileges: true}).privileges.length);
     })();

    (function testRevokePrivilegesFromRole() {
         jsTestLog("Testing revokePrivilegesFromRole");

         // Try to revoke a privilege the role doesn't have
         // Should not error but should do nothing.
         assert.doesNotThrow(function() {
                                 db.revokePrivilegesFromRole("role3",
                                                             [{resource: {db: "test",
                                                                          collection: "foobar"},
                                                               actions: ["insert"]}]);
                       });
         assert.eq(0, db.getRole("role2", {showPrivileges: true}).privileges.length);

         // Must revoke something
         assert.throws(function() {
                           db.revokePrivilegesFromRole("role3", []);
                       });

         // Try to remove from built-in role
         assert.throws(function() {
                           db.revokePrivilegesFromRole("readWrite", [{resource: {db: 'test',
                                                                                 collection: ''},
                                                                      actions: ['find']}]);
                       });

         var basicPriv = {resource: {db: 'test', collection:""}, actions: ['find']};

          // Invalid first argument
         assert.throws(function() {
                           db.revokePrivilegesFromRole(["role3", "role2"], [basicPriv]);
                       });

         // Try to revoke from role that doesn't exist
         assert.throws(function() {
                           db.revokePrivilegesToRole("fakeRole", [basicPriv]);
                       });

         // Test with invalid privileges
         var badPrivs = [];
         badPrivs.push("find");
         badPrivs.push({resource: {db: 'test', collection:""}, actions: ['fakeAction']});
         badPrivs.push({resource: {db: ['test'], collection:""}, actions: ['find']});
         badPrivs.push({resource: {db: 'test', collection:""}});
         badPrivs.push({actions: ['find']});
         badPrivs.push({resource: {db: 'test', collection:""}, actions: []});
         badPrivs.push({resource: {db: 'test'}, actions: ['find']});
         badPrivs.push({resource: {collection: 'test'}, actions: ['find']});
         badPrivs.push({resource: {}, actions: ['find']});
         badPrivs.push({resource: {db: 'test', collection:"", cluster:true }, actions: ['find']});

         for (var i = 0; i < badPrivs.length; i++) {
             assert.throws(function() {
                               db.revokePrivilegesFromRole("role3", [badPrivs[i]]);
                           });
         }

         assert.eq(1, db.getRole('role3', {showPrivileges: true}).privileges.length);
     })();

    (function testRolesInfo() {
         jsTestLog("Testing rolesInfo");

         // Try to get role that does not exist
         assert.eq(null, db.getRole('fakeRole'));

         // Pass wrong type for role name
         assert.throws(function() {
                           db.getRole(5);
                       });

         assert.throws(function() {
                           db.getRole([]);
                       });

         assert.throws(function() {
                           db.getRole(['role1', 'role2']);
                       });
     })();

    (function testDropRole() {
         jsTestLog("Testing dropRole");

         // Try to drop a role that doesn't exist
         // Should not error but should do nothing
         assert.doesNotThrow(function() {
                                 db.dropRole('fakeRole');
                             });

         // Try to drop a built-in role
         assert.throws(function() {
                           db.dropRole('read');
                       });

         assert.eq(3, db.getRoles().length);
     })();

    // dropAllRolesFromDatabase ignores its arguments, so there's nothing to test for it.
}

jsTest.log('Test standalone');
var conn = MongoRunner.runMongod({ auth: '' });
runTest(conn);
MongoRunner.stopMongod(conn.port);

jsTest.log('Test sharding');
var st = new ShardingTest({ shards: 2, config: 3, keyFile: 'jstests/libs/key1' });
runTest(st.s);
st.stop();
